use serde::{Deserialize, Serialize};
use std::io;
use thiserror::Error;

pub const DEFAULT: i64 = 55;
pub const ENV: &str = "RUST";

pub fn format<T: Formatter + Checker>(item: &T) -> String {
    if item.check() {
        return item.format();
    }
    "".to_string()
}

pub trait Formatter {
    fn format(&self) -> String;
}

pub trait Checker {
    fn check(&self) -> bool;
}

#[derive(Debug)]
pub enum BookType {
    Go(String),
    Rust,
}

impl BookType {
    pub fn call(&self) {
        println!("book type:{:?}", self)
    }
    pub fn to_string(&self) -> String {
        match self {
            BookType::Go(_) => "Go".to_string(),
            BookType::Rust => "Rust".to_string(),
        }
    }
}

#[derive(Debug)]
pub struct Book {
    pub id: u64,
    pub title: String,
    pub created_at: i64,
    pub updated_at: i64,
    pub book_type: BookType,
}

impl Formatter for Book {
    fn format(&self) -> String {
        format!("Book, id:{}, title:{}", self.id, self.title)
    }
}

impl Checker for Book {
    fn check(&self) -> bool {
        if self.title.len() > 0 {
            return true;
        }
        false
    }
}

#[derive(Debug)]
pub struct BookError {
    pub code: usize,
    pub message: String,
}

#[derive(Error, Debug)]
pub enum ReadFileError {
    #[error("io error")]
    IOError(#[from] io::Error),
    #[error("unknown error")]
    Unknown,
    #[error("open failed")]
    OpenFailed,
    #[error("create failed")]
    CreateFailed,
    #[error("read failed")]
    ReadFailed,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct User {
    pub id: u64,
    pub name: String,
    #[serde(rename = "category")]
    pub category: UserCategory,
    pub option: Option<String>,
}

#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub enum UserCategory {
    #[serde(rename = "go")]
    Go,
    #[serde(rename = "rust")]
    Rust(Rust),
}

#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct Rust {
    pub url: String,
    pub age: u8,
}

impl User {
    pub fn new(id: u64, name: &str, category: UserCategory, option: Option<String>) -> User {
        User {
            id: id,
            name: name.to_string(),
            category: category,
            option: option,
        }
    }
}

impl Formatter for User {
    fn format(&self) -> String {
        format!("User, id:{}, name:{}", self.id, self.name)
    }
}

#[cfg(test)]
mod test {
    use super::{Rust, User, UserCategory};
    use serde_json;

    #[test]
    fn json() {
        let json_str = r#"
        {
            "id": 1,
            "name": "test",
            "category": {
                "rust": {
                    "url": "https://rust-lang.org",
                    "age": 1
                }
            },
            "option": "test"
        }"#;

        let json_u: User = serde_json::from_str(json_str).unwrap();

        let id = 1u64;
        let name = "test";
        let category = UserCategory::Rust(Rust {
            url: "https://rust-lang.org".to_string(),
            age: 1,
        });
        let option = Some(name.to_string());

        let u = User::new(id, name, category, option);

        let u_str = serde_json::to_string(&u).unwrap();
        println!("user str:{}", u_str);

        let u2: User = serde_json::from_str(&u_str).unwrap();
        assert_eq!(u, u2);
        assert_eq!(json_u, u2);

        let json_str = r#"{"id":1,"name":"test","category":"go","option":null}"#;
        let json_u: User = serde_json::from_str(json_str).unwrap();

        let category = UserCategory::Go;
        let option = None;
        let u = User::new(id, name, category, option);

        let u_str = serde_json::to_string(&u).unwrap();
        println!("user str:{}", u_str);
        assert_eq!(json_u, u);
    }
}
